package ogmios.dialogs;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.regex.*;
import javax.swing.text.*;
import ogmios.core.*;
/**
 * This class provides a simple Find and Replace Dialog to search through a JTextComponent.
 * A warning though -- Find And Replace will not function as expected with a component
 * that has strange newlines. A workaround is to extend the class, override the getText() method,
 * and return the text directly from the JTextComponent's document.
 *
 */
public class FindAndReplaceDialog extends JDialog
{
  private final JComboBox scope;
  private final JButton find;
  private final JButton replace;
  private final JButton replaceAll;
  private final JTextField target;
  private final JTextField replacement;
  private final JCheckBox matchWholeWord;
  private final JCheckBox caseSensitive;
  private final JCheckBox useRegex;
  private final JLabel targetLabel;
  private final JLabel replacementLabel;
  private JTextComponent text;
  private Window host;
  private Pattern search;
  private Matcher matcher;
  private String pattern = "";
  private int firstStart, start, end;
  private boolean hasWrapped = true;
  private static final int CASE_SENSITIVE = 2;
  private static final int USE_REGEX = 4;
  private static final int WHOLE_WORD = 8;

  private static final int CURRENT_DOCUMENT = 0;
  private static final int SELECTION  = 1;
  private int scopeType = 0;
  private int mask = 0;
  private int currentMask = 0;
  public FindAndReplaceDialog()
  {
    setModal(true);
    setLayout(new GridBagLayout());
    scope = new JComboBox();
    scope.addItem("Current Document");
    scope.addItem("Selected Text");
    target = (JTextField)OgmiosUtilities.undoable(new JTextField("", 20));
    replacement = (JTextField)OgmiosUtilities.undoable(new JTextField("", 20));
    matchWholeWord = new JCheckBox ("Whole Word", false);
    caseSensitive = new JCheckBox("Match Case", false);
    useRegex = new JCheckBox ("Use Regex", false);
    find = new JButton("Find Next");
    replace = new JButton("Replace");
    replaceAll = new JButton("Replace All");
    targetLabel = new JLabel("Find...");
    replacementLabel = new JLabel("Replace with...");

    addWindowListener(
      new WindowAdapter()
    {

      public void windowActivated(WindowEvent e)
      {

        /*	if (e.getOppositeWindow() != null || )
        	{*/

        Component focusOwner = host.getMostRecentFocusOwner();

        if (focusOwner instanceof JTextComponent)
        {
          target.requestFocus();
          text = (JTextComponent)focusOwner;

          boolean enabled = text.isEditable();
          replace.setVisible(enabled);
          replaceAll.setVisible(enabled);
          replacement.setVisible(enabled);
          replacementLabel.setVisible(enabled);
          setTitle((enabled ? "Find/Replace " : "Find ") + text.getName());
          //Force pattern regeneration
          mask  = -3;

          start = text.getSelectionStart();

          firstStart = start;
          end = text.getSelectionEnd();
          hasWrapped = false;
          if (start != end)
          {
            if (scope.getItemCount() == 1)
              scope.addItem("Selected Text");

            scope.setSelectedIndex(text.getSelectedText().equals(target.getText()) ? 0 : 1);

          }
          else
          {

            if (scope.getItemCount() == 2)
              scope.removeItemAt(1);
            scope.setSelectedIndex(0);
          }
          if (start == text.getText().length())
          {
            start = 0;
            firstStart = start;
          }
        }


//	}






      }

    }
    );


    useRegex.addItemListener(
      new ItemListener()
    {
      public void itemStateChanged(ItemEvent e)
      {
        matchWholeWord.setEnabled(!useRegex.isSelected());
      }
    }
    );

    AbstractAction findAction =
      new AbstractAction()
    {
      public void actionPerformed(ActionEvent e)
      {
        try
        {
          String targetText = target.getText();
          String searchText = text.getText();
          int targetLength = targetText.length();
          int textLength = searchText.length();
          if (targetLength > 0 && targetLength <= textLength)
          {

            //Since compiling regexes can get expensive, we check first to see if
            //we can utilize the previous compilation
            if (mask != currentMask || pattern.equals(targetText) == false)
            {
              mask = currentMask;
              pattern = targetText;
              if (useRegex.isSelected())
                if (caseSensitive.isSelected() == false)
                  search = Pattern.compile(targetText, Pattern.CASE_INSENSITIVE);
                else
                  search = Pattern.compile(target.getText());
              else if (caseSensitive.isSelected())
                if (matchWholeWord.isSelected())
                  search = Pattern.compile("\\b" + Pattern.quote(targetText) + "\\b");
                else
                  search = Pattern.compile(targetText, Pattern.LITERAL);
              else if (matchWholeWord.isSelected())
              {
                search = Pattern.compile("\\b" + targetText + "\\b", Pattern.CASE_INSENSITIVE);
              }
              else search = Pattern.compile(targetText, Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
              //May or may not keep
              if (start >= textLength)
              {
                firstStart = 0;
                start = 0;
              }
              else
              {
                firstStart = start;
              }
              hasWrapped = false;
              matcher = search.matcher(searchText);
            }

            if (scopeType == CURRENT_DOCUMENT)
            {
              if (firstStart == 0)
              {
                if (search(start, textLength) == false)
                {
                  JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                  start = firstStart;
                }
              }
              else
              {
                if (hasWrapped)
                {
                  if (searchInside(start, firstStart) == false)
                  {
                    JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                    hasWrapped = false;
                    start = firstStart;
                  }

                }
                else
                {
                  if (search(start, textLength) == false)
                  {
                    hasWrapped = true;
                    if (searchInside(0, firstStart) == false)
                    {
                      JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                      hasWrapped = false;
                      start = firstStart;
                    }
                  }
                }
              }
            }
            else
            {
              if (start > end || search(start, end) == false)
              {
                JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                start = firstStart;
              }
            }



          }
        }
        catch (Throwable ex)
        {
          OgmiosUtilities.showErrorBox(FindAndReplaceDialog.this, ex, "Find:Search Error");

        }
      }

    };
    find.addActionListener(findAction);
    InputMap findInputMap = find.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
    findInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "find");;
    ActionMap findActionMap = find.getActionMap();
    findActionMap.put("find", findAction);

    replace.addActionListener(
      new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        try
        {
          String targetText = target.getText();
          String searchText = text.getText();
          String replaceText = replacement.getText();
          int replaceLength = replaceText.length();
          int targetLength = targetText.length();
          int textLength = searchText.length();
          if (targetLength > 0 && targetLength <= textLength)
          {
            if (mask != currentMask || pattern.equals(targetText) == false)
            {
              mask = currentMask;
              pattern = targetText;
              if (useRegex.isSelected())
                if (caseSensitive.isSelected() == false)
                  search = Pattern.compile(targetText, Pattern.CASE_INSENSITIVE);
                else
                  search = Pattern.compile(target.getText());
              else if (caseSensitive.isSelected())
                if (matchWholeWord.isSelected())
                  search = Pattern.compile("\\b" + Pattern.quote(targetText) + "\\b");
                else
                  search = Pattern.compile(targetText, Pattern.LITERAL);
              else if (matchWholeWord.isSelected())
                search = Pattern.compile("\\b" + Pattern.quote(targetText) + "\\b", Pattern.CASE_INSENSITIVE);

              else search = Pattern.compile(targetText, Pattern.LITERAL | Pattern.CASE_INSENSITIVE);

              matcher = search.matcher(searchText);
              if (start >= textLength)
              {
                firstStart = 0;
                start = 0;
              }
              else
              {
                firstStart = start;
              }
              hasWrapped = false;
            }
            //Are we already highlighting something?
            int selectionStart = text.getSelectionStart();
            int selectionEnd = text.getSelectionEnd();
            if (selectionStart != selectionEnd)
            {
              if (search(selectionStart, selectionEnd))
              {
                text.replaceSelection(replaceText);

                matcher = search.matcher(searchText = text.getText());
                textLength = searchText.length();
                if (start >= textLength)
                {
                  start = 0;
                  hasWrapped = true;
                }
                if (FindAndReplaceDialog.this.start < FindAndReplaceDialog.this.end || hasWrapped == false)
                {
//Beginning

                  if (scopeType == CURRENT_DOCUMENT)
                  {
                    if (firstStart == 0)
                    {
                      if (search(start, textLength) == false)
                      {
                        JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                        start = firstStart;
                      }
                    }
                    else
                    {
                      if (hasWrapped)
                      {
                        if (searchInside(start, firstStart) == false)
                        {
                          JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                          hasWrapped = false;
                          start = firstStart;
                        }

                      }
                      else
                      {
                        if (search(start, textLength) == false)
                        {
                          hasWrapped = true;
                          if (searchInside(0, firstStart) == false)
                          {
                            JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                            hasWrapped = false;
                            start = firstStart;
                          }
                        }
                      }
                    }
                  }
                  else
                  {
                    if (start > end || search(start, end) == false)
                    {
                      JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                      start = firstStart;
                    }
                  }



//End
                }


              }
              else
              {

//Second Beginning

                if (scopeType == CURRENT_DOCUMENT)
                {
                  if (firstStart == 0)
                  {
                    if (search(start, textLength) == false)
                    {
                      JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                      start = firstStart;
                    }
                  }
                  else
                  {
                    if (hasWrapped)
                    {
                      if (searchInside(start, firstStart) == false)
                      {
                        JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                        hasWrapped = false;
                        start = firstStart;
                      }

                    }
                    else
                    {
                      if (search(start, textLength) == false)
                      {
                        hasWrapped = true;
                        if (searchInside(0, firstStart) == false)
                        {
                          JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                          hasWrapped = false;
                          start = firstStart;
                        }
                      }
                    }
                  }
                }
                else
                {
                  if (start > end || search(start, end) == false)
                  {
                    JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                    start = firstStart;
                  }
                }

//second ending

              }
            }
            //If not...well, get looking!
            else
            {

              if (scopeType == CURRENT_DOCUMENT)
              {
                if (firstStart == 0)
                {
                  if (search(start, textLength) == false)
                  {
                    JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                    start = firstStart;
                  }
                }
                else
                {
                  if (hasWrapped)
                  {
                    if (searchInside(start, firstStart) == false)
                    {
                      JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                      hasWrapped = false;
                      start = firstStart;
                    }

                  }
                  else
                  {
                    if (search(start, textLength) == false)
                    {
                      hasWrapped = true;
                      if (searchInside(0, firstStart) == false)
                      {
                        JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                        hasWrapped = false;
                        start = firstStart;
                      }
                    }
                  }
                }
              }
              else
              {
                if (start > end || search(start, end) == false)
                {
                  JOptionPane.showMessageDialog(FindAndReplaceDialog.this, "Finished Searching!", "Find", JOptionPane.INFORMATION_MESSAGE);
                  start = firstStart;
                }
              }

            }


          }
        }
        catch (Throwable ex)
        {
          OgmiosUtilities.showErrorBox(FindAndReplaceDialog.this, ex, "Replace:Search Error");
        }
      }
    }
    );


    replaceAll.addActionListener(
      new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        try
        {
          String targetText = target.getText();
          String searchText = text.getText();
          String replaceText = replacement.getText();
          int targetLength = targetText.length();
          int textLength = searchText.length();
          Document textDocument = text.getDocument();
          if (targetLength > 0 && targetLength <= textLength)
          {
            if (mask != currentMask || pattern.equals(targetText) == false)
            {
              mask = currentMask;
              pattern = targetText;
              if (useRegex.isSelected())
                if (caseSensitive.isSelected() == false)
                  search = Pattern.compile(targetText, Pattern.CASE_INSENSITIVE);
                else
                  search = Pattern.compile(target.getText());
              else if (caseSensitive.isSelected())
                if (matchWholeWord.isSelected())
                  search = Pattern.compile("\\b" + Pattern.quote(targetText) + "\\b");
                else
                  search = Pattern.compile(targetText, Pattern.LITERAL);
              else if (matchWholeWord.isSelected())
                search = Pattern.compile("\\b" + Pattern.quote(targetText) + "\\b", Pattern.CASE_INSENSITIVE);

              else search = Pattern.compile(targetText, Pattern.LITERAL | Pattern.CASE_INSENSITIVE);

              matcher = search.matcher(searchText);
            }
            int count = 0;
            int selectionStart = text.getSelectionStart();
            int selectionEnd = text.getSelectionEnd();
            if (selectionStart == selectionEnd || scopeType != SELECTION)
            {
              int position = 0;
              while (position <= textDocument.getLength() && matcher.find(position))
              {
                count++;
                try
                {
                  int start = matcher.start();
                  int end = matcher.end();
                  position = start + replaceText.length();
                  textDocument.remove(start, end - start);
                  textDocument.insertString(start, replaceText, null);
                  matcher = search.matcher(text.getText());
                }
                catch (BadLocationException ble)
                {

                }
              }
            }
            else
            {
              int position = selectionStart;
              while (position <= textLength && matcher.find(position))
              {

                try
                {
                  int end = matcher.end();
                  if (end > selectionEnd)
                  {
                    break;
                  }
                  count++;
                  int start = matcher.start();
                  position = start + replaceText.length();
                  textDocument.remove(start, end - start);
                  textDocument.insertString(start, replaceText, null);
                  selectionEnd += textDocument.getLength() - textLength;
                  textLength = textDocument.getLength();
                  matcher = search.matcher(text.getText());

                }
                catch (BadLocationException ble)
                {

                }

              }
            }
            JOptionPane.showMessageDialog(FindAndReplaceDialog.this, count != 1 ? "Made " + count + " replacements" : "Made 1 replacement", "Replace All", JOptionPane.INFORMATION_MESSAGE);
          }
        }
        catch (Throwable ex)
        {
          OgmiosUtilities.showErrorBox(FindAndReplaceDialog.this, ex, "Replace-All:Search Error");
        }
      }
    }
    );

    GridBagConstraints constraints = new GridBagConstraints();
    Insets defaultInsets = constraints.insets;
    /*Dialog
     * TextFields	Buttons
     * Options
     *
     */


    /*Text Fields
     * Find: [====]
     * Replace: [====]
     */
    constraints.gridy = 0;
    constraints.gridx = 0;
    Container content = getContentPane();
    constraints.anchor = GridBagConstraints.FIRST_LINE_START;
    content.add(targetLabel, constraints);
    constraints.gridx = 1;
    content.add(target, constraints);
    constraints.gridy = 1;
    constraints.insets = new Insets(3, 0, 0, 0);
    content.add(replacement, constraints);
    constraints.gridx = 0;
    content.add(replacementLabel, constraints);
    constraints.insets = defaultInsets;

    /*Options
     *  -Options--------------------
     * | Scope:		|
     * | [Selection]                   	|
     * | [] Case Sensitive           |
     * | [] Match Whole Word 	|
     * | [] Use Regex                	|
     * ---------------------------------
     */
    JPanel options = new JPanel();
    options.setLayout(new BoxLayout(options, BoxLayout.PAGE_AXIS));
    options.setBorder(BorderFactory.createTitledBorder("Options"));
    options.add(new JLabel("Search Domain:", SwingConstants.LEFT));
    options.add(scope);
    scope.setAlignmentX(Component.LEFT_ALIGNMENT);
    options.add(caseSensitive);
    caseSensitive.setAlignmentX(Component.LEFT_ALIGNMENT);
    options.add(matchWholeWord);
    matchWholeWord.setAlignmentX(Component.LEFT_ALIGNMENT);
    options.add(useRegex);
    useRegex.setAlignmentX(Component.LEFT_ALIGNMENT);
    constraints.gridy = 2;
    constraints.gridwidth = 2;
    content.add(options, constraints);


    /*Buttons
     * [Find]
     * [Replace]
     * [Replace All]
     */
    constraints.insets = new Insets(0, 9, 0, 0);
//constraints.weightx=.5;
    constraints.gridx = 2;
    constraints.gridy = 0;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.gridwidth = 1;
    content.add(find, constraints);
    constraints.gridy = 1;
    constraints.insets = new Insets(3, 9, 0, 0);
    content.add(replace, constraints);
    constraints.gridy = 2;
    content.add(replaceAll, constraints);
    pack();
    setResizable(false);

    AbstractAction escapeAction =
      new AbstractAction()
    {
      public void actionPerformed(ActionEvent e)
      {
        setVisible(false);
      }
    };
    findInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape");
    findActionMap.put("escape", escapeAction);

    ItemListener maskBuilder =
      new ItemListener()
    {
      public void itemStateChanged(ItemEvent e)
      {

        currentMask = useRegex.isSelected() ?
                      caseSensitive.isSelected() ? USE_REGEX | CASE_SENSITIVE :
                      USE_REGEX :
                      caseSensitive.isSelected() ?
                      matchWholeWord.isSelected() ? CASE_SENSITIVE | WHOLE_WORD :
                      CASE_SENSITIVE :
                      matchWholeWord.isSelected() ? WHOLE_WORD : 0;

      }
    };

    FocusListener textFieldFocus =
      new FocusAdapter()
    {
      public void focusGained(FocusEvent e)
      {
        ((JTextField)e.getSource()).selectAll();
      }
    };

    target.addFocusListener(textFieldFocus);
    replacement.addFocusListener(textFieldFocus);


    useRegex.addItemListener(maskBuilder);
    caseSensitive.addItemListener(maskBuilder);
    matchWholeWord.addItemListener(maskBuilder);
    scope.addActionListener(
      new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        scopeType = scope.getSelectedItem().equals("Selected Text") ? SELECTION : CURRENT_DOCUMENT;
      }
    }
    );

  }

  public void showDialog(Window host)
  {

    setLocationRelativeTo(this.host = host);
    setVisible(true);
  }

  public boolean search(int start, int end)
  {
    String target = FindAndReplaceDialog.this.target.getText();
    String text = FindAndReplaceDialog.this.text.getText();
    if (matcher.find(start) && matcher.end() <= end)
    {
      FindAndReplaceDialog.this.text.select(matcher.start(), matcher.end());
      FindAndReplaceDialog.this.start = matcher.start() + 1;



      return true;
    }
    return false;
  }
  public boolean searchInside(int start, int end)
  {
    String target = FindAndReplaceDialog.this.target.getText();
    String text = FindAndReplaceDialog.this.text.getText();
    if (matcher.find(start) && matcher.start() < end)
    {
      FindAndReplaceDialog.this.text.select(matcher.start(), matcher.end());
      FindAndReplaceDialog.this.start = matcher.start() + 1;
      return true;
    }
    return false;
  }
}